// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/updater/win/setup/setup.h"

#include <shlobj.h>

#include <memory>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/version.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/win_util.h"
#include "chrome/installer/util/self_cleaning_temp_dir.h"
#include "chrome/installer/util/work_item_list.h"
#include "chrome/updater/app/server/win/updater_idl.h"
#include "chrome/updater/app/server/win/updater_internal_idl.h"
#include "chrome/updater/app/server/win/updater_legacy_idl.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util.h"
#include "chrome/updater/win/setup/setup_util.h"
#include "chrome/updater/win/task_scheduler.h"
#include "chrome/updater/win/win_constants.h"
#include "chrome/updater/win/win_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace updater {
namespace {

// Adds work items to register the per-user internal COM Server with Windows.
void AddComServerWorkItems(const base::FilePath& com_server_path,
                           WorkItemList* list) {
  DCHECK(list);
  if (com_server_path.empty()) {
    LOG(DFATAL) << "com_server_path is invalid.";
    return;
  }

  for (const auto& clsid : GetSideBySideServers(UpdaterScope::kUser)) {
    AddInstallServerWorkItems(HKEY_CURRENT_USER, clsid, com_server_path, true,
                              list);
  }
}

// Adds work items to register the COM Interfaces with Windows.
void AddComInterfacesWorkItems(HKEY root,
                               const base::FilePath& typelib_path,
                               WorkItemList* list) {
  DCHECK(list);

  for (const auto& iid : GetSideBySideInterfaces()) {
    AddInstallComInterfaceWorkItems(root, typelib_path, iid, list);
  }
}

// Returns a list of base file names which the setup copies to the install
// directory. The source of these files is either the unpacked metainstaller
// archive, or the `out` directory of the build, if a command line argument is
// present. In the former case, which is the normal execution flow, the files
// are enumerated from the directory where the metainstaller unpacked its
// contents. In the latter case, the file containing the run time dependencies
// of the updater (which is generated by GN at build time) is parsed, and the
// relevant file names are extracted.
std::vector<base::FilePath> GetSetupFiles(const base::FilePath& source_dir) {
  const auto* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(kInstallFromOutDir)) {
    return ParseFilesFromDeps(source_dir.Append(FILE_PATH_LITERAL(
        "gen\\chrome\\updater\\win\\installer\\updater.runtime_deps")));
  }
  std::vector<base::FilePath> result;
  base::FileEnumerator it(
      source_dir, false, base::FileEnumerator::FileType::FILES,
      FILE_PATH_LITERAL("*"), base::FileEnumerator::FolderSearchPolicy::ALL,
      base::FileEnumerator::ErrorPolicy::STOP_ENUMERATION);
  for (base::FilePath file = it.Next(); !file.empty(); file = it.Next()) {
    result.push_back(file.BaseName());
  }
  if (it.GetError() != base::File::Error::FILE_OK) {
    VLOG(2) << __func__ << " could not enumerate files : " << it.GetError();
    return {};
  }
  return result;
}

}  // namespace

// TODO(crbug.com/1069976): use specific return values for different code paths.
int Setup(UpdaterScope scope) {
  VLOG(1) << __func__ << ", scope: " << scope;
  DCHECK(scope == UpdaterScope::kUser || ::IsUserAnAdmin());
  HKEY key;
  switch (scope) {
    case UpdaterScope::kSystem:
      key = HKEY_LOCAL_MACHINE;
      break;
    case UpdaterScope::kUser:
      key = HKEY_CURRENT_USER;
      break;
  }

  auto scoped_com_initializer =
      std::make_unique<base::win::ScopedCOMInitializer>(
          base::win::ScopedCOMInitializer::kMTA);

  base::FilePath temp_dir;
  if (!base::GetTempDir(&temp_dir)) {
    LOG(ERROR) << "GetTempDir failed.";
    return -1;
  }
  const absl::optional<base::FilePath> versioned_dir =
      GetVersionedDirectory(scope);
  if (!versioned_dir) {
    LOG(ERROR) << "GetVersionedDirectory failed.";
    return -1;
  }
  base::FilePath exe_path;
  if (!base::PathService::Get(base::FILE_EXE, &exe_path)) {
    LOG(ERROR) << "PathService failed.";
    return -1;
  }

  installer::SelfCleaningTempDir backup_dir;
  if (!backup_dir.Initialize(temp_dir, L"updater-backup")) {
    LOG(ERROR) << "Failed to initialize the backup dir.";
    return -1;
  }

  const auto source_dir = exe_path.DirName();
  const auto setup_files = GetSetupFiles(source_dir);
  if (setup_files.empty()) {
    LOG(ERROR) << "No files to set up.";
    return -1;
  }

  // All source files are installed in a flat directory structure inside the
  // versioned directory, hence the BaseName function call below.
  std::unique_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList());
  for (const auto& file : setup_files) {
    const base::FilePath target_path = versioned_dir->Append(file.BaseName());
    const base::FilePath source_path = source_dir.Append(file);
    install_list->AddCopyTreeWorkItem(source_path, target_path, temp_dir,
                                      WorkItem::ALWAYS);
  }

  for (const auto& key_path :
       {GetRegistryKeyClientsUpdater(), GetRegistryKeyClientStateUpdater()}) {
    install_list->AddCreateRegKeyWorkItem(key, key_path, KEY_WOW64_32KEY);
    install_list->AddSetRegValueWorkItem(key, key_path, KEY_WOW64_32KEY,
                                         kRegValuePV, kUpdaterVersionUtf16,
                                         true);
    install_list->AddSetRegValueWorkItem(
        key, key_path, KEY_WOW64_32KEY, kRegValueName,
        base::ASCIIToWide(PRODUCT_FULLNAME_STRING), true);
  }

  static constexpr base::FilePath::StringPieceType kUpdaterExe =
      FILE_PATH_LITERAL("updater.exe");

  AddComInterfacesWorkItems(key, versioned_dir->Append(kUpdaterExe),
                            install_list.get());
  switch (scope) {
    case UpdaterScope::kUser:
      AddComServerWorkItems(versioned_dir->Append(kUpdaterExe),
                            install_list.get());
      break;
    case UpdaterScope::kSystem:
      AddComServiceWorkItems(versioned_dir->Append(kUpdaterExe), true,
                             install_list.get());
      break;
  }

  base::CommandLine run_updater_wake_command(
      versioned_dir->Append(kUpdaterExe));
  run_updater_wake_command.AppendSwitch(kWakeSwitch);
  if (scope == UpdaterScope::kSystem)
    run_updater_wake_command.AppendSwitch(kSystemSwitch);
  run_updater_wake_command.AppendSwitch(kEnableLoggingSwitch);
  run_updater_wake_command.AppendSwitchASCII(kLoggingModuleSwitch,
                                             kLoggingModuleSwitchValue);
  if (!install_list->Do() ||
      !RegisterWakeTask(run_updater_wake_command, scope)) {
    LOG(ERROR) << "Install failed, rolling back...";
    install_list->Rollback();
    UnregisterWakeTask(scope);
    LOG(ERROR) << "Rollback complete.";
    return -1;
  }

  VLOG(1) << "Setup succeeded.";
  return 0;
}

}  // namespace updater
